#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include "vector_binario.h"
#include "ManejadorBits.h"

/* ******************************************************************
 *                DEFINICION DE LOS TIPOS DE DATOS					*
 * *****************************************************************/

/* bitsOcupados indica la cantidad de bits ocupados en el buffer de escritura
 * o la cantidad de bits del buffer ya leidos en modo lectura */
struct _manejador_bits {
	FILE* archivo;
	unsigned char buffer;
	int bitsOcupados;
	bool escritura;
};

/* *********************************************************************
 *                	 	  FUNCIONES AUXILIARES						   *
 * *********************************************************************/

/* Baja el buffer a disco y pone uno vacío en memoria */
void ManejadorBits_bajar_buffer (ManejadorBits_t* manejador) {
	if (!manejador) return;
	if (!(manejador->archivo)) return;
	
	fwrite ( (const void*) &(manejador->buffer), sizeof (char), 1, manejador->archivo );
	manejador->buffer = 0;
	manejador->bitsOcupados = 0;
}

/* *********************************************************************
 *					  PRIMITIVAS DEL MANEJADOR BITS					   *
 * *********************************************************************/

/* Crea el manejador de archivos en bits. Devuelve NULL en caso de error. */
ManejadorBits_t* ManejadorBits_crear (char* ruta, bool escrituraParametro) {
	
	if (!ruta)  return NULL;

	ManejadorBits_t* manejador = malloc ( sizeof (ManejadorBits_t) );
	if (!manejador) return NULL;
	
	manejador->buffer = 0;
	manejador->bitsOcupados = 0;
	manejador->escritura = escrituraParametro;
	
	if (manejador->escritura) manejador->archivo = fopen (ruta, "wb");
		else manejador->archivo = fopen (ruta, "rb");
	
	if (!manejador->archivo) {
		free (manejador);
		return NULL;
	}
	
	return manejador;
}

/* Devuelve true si el buffer está vacío, false en cualquier otro caso. */
bool ManejadorBits_buffer_esta_vacio (ManejadorBits_t* manejador) {
	if (!manejador) return false;
	if ( (manejador->bitsOcupados) > 0 ) return false;
	return true;
}

/* Devuelve true si el buffer está lleno, false en cualquier otro caso. */
bool ManejadorBits_buffer_esta_lleno (ManejadorBits_t* manejador) {
	if (!manejador) return false;
	if ( (manejador->bitsOcupados) == 8 ) return true;
	return false;
}

/* Escribe el número en el archivo. Devuelve false en caso de error. */
bool ManejadorBits_escribir_numero (ManejadorBits_t* manejador, vector_binario_t* numero) {
	
	if ( (!manejador) || (!numero) ) return false;
	
	if (ManejadorBits_buffer_esta_lleno (manejador)) {
		ManejadorBits_bajar_buffer (manejador);
	}
	int potenciaInicial = ( 7 - (manejador->bitsOcupados) );
	int bitEscrito = (int) pow (2, potenciaInicial); // 2 ^ potenciaInicial
	int largo = v_binario_largo (numero);
	
	for ( int i = 0 ; i < largo ; i++ ) {
		int bitLeido = v_binario_obtener (numero, i);
		manejador->buffer = ( (manejador->buffer) | (bitLeido * bitEscrito) );
		(manejador->bitsOcupados)++;
		bitEscrito /= 2;
		
		if ((manejador->bitsOcupados) == 8) {
			ManejadorBits_bajar_buffer (manejador);
			bitEscrito = 128; // 2 ^ 7
		}
	}
	
	return true;
}

/* Escribe la cadena en el archivo. Devuelve false en caso de error. */
bool ManejadorBits_escribir_cadena (ManejadorBits_t* manejador, char* cadena) {
	if ( (!manejador) || (!cadena) ) return false;
	
	if (ManejadorBits_buffer_esta_lleno (manejador)) {
		ManejadorBits_bajar_buffer (manejador);
	}
	
	int shift = (manejador->bitsOcupados);
	//printf("%d ", shift);
	int largo = strlen (cadena);
	for ( int i = 0 ; i < largo ; i++ ) {
		char caracter = cadena[i];
		//printf("%d ", shift);
	
		
		char c_shift_der = ( caracter >> shift );
		manejador->buffer = ( (manejador->buffer) | c_shift_der );
		ManejadorBits_bajar_buffer (manejador);
		
		
		char c_shift_izq = ( caracter << (8 - shift) );
		manejador->buffer = ( (manejador->buffer) | c_shift_izq );
		
	}
	(manejador->bitsOcupados) = shift;
	return true;
}

/* Devuelve true si el manejador fue creado en modo escritura, false
 * en cualquier otro caso. */
bool ManejadorBits_es_modo_escritura (ManejadorBits_t* manejador) {
	if (!manejador) return false;
	return (manejador->escritura);
}

/* Hace una lectura del disco y carga la información en el buffer.
 * Devuelve false en caso de error. */
bool ManejadorBits_cargar_buffer (ManejadorBits_t* manejador) {
	if (!manejador) return false;
	if ( (manejador->escritura) || (!(manejador->archivo)) ) return false;
	
	fread ( &(manejador->buffer), sizeof (char) , 1 , manejador->archivo );
	
	if (feof (manejador->archivo) != 0 ) return false;
	
	manejador->bitsOcupados = 0;
	return true;
}

/* Posiciona el file handler en 'posicion' (expresado en bits). El manejador
 * debe estar en formato lectura. Devuelve false en caso de error. */
bool ManejadorBits_seek (ManejadorBits_t* manejador, long int posicion) {
	if (!manejador) return false;
	if ( (!(manejador->archivo)) || (manejador->escritura) ) return false;
	if (posicion < 0) return false;
	
	// voy al byte más cercano
	long int posByte = ( posicion / 8 );
	int resultado = fseek (manejador->archivo, posByte, SEEK_SET);
	if (resultado != 0) return false;
	
	// cargo el buffer
	if ( !((posByte*8) == posicion) )
		if ( !(ManejadorBits_cargar_buffer (manejador)) ) return false;
	
	// marco los bits leidos
	int resto = posicion % 8;
	manejador->bitsOcupados = resto;
	
	return true;
}

/* Devuelve la cantidad de bits ocupados en el buffer o -1 en caso de error. */
int ManejadorBits_bits_ocupados (ManejadorBits_t* manejador) {
	if (!manejador) return -1;
	return (manejador->bitsOcupados);
}

/* Devuelve el buffer en formato char. Devuelve 0 en caso de error.
 * Se debe utilizar la primitiva ".._buffer_esta_vacio" para distinguir
 * entre un buffer vacío y un error al ver el buffer. */
char ManejadorBits_ver_buffer (ManejadorBits_t* manejador) {
	if (!manejador) return 0;
	return (manejador->buffer);
}

/* Ocupa la cantidad de bits del buffer indicado por el parámetro.
 * Devuelve false en caso de error. */
bool ManejadorBits_ocuparBits (ManejadorBits_t* manejador, int bits) {
	if (!manejador) return false;
	manejador->bitsOcupados = bits;
	return true;
} 

char* ManejadorBits_leer_cadena (ManejadorBits_t* manejador, long largo) {
	bool status = true;
	if (!manejador) return NULL;
	if ( (manejador->escritura) || (!(manejador->archivo)) ) return NULL;
	
	char* cadena = malloc ( sizeof(char) * (largo+1) );
	unsigned  char bufferSuperior;
	unsigned char bufferInferior;
	if (!cadena) return NULL;
	
	if (ManejadorBits_buffer_esta_vacio (manejador)) {
		status = ManejadorBits_cargar_buffer (manejador);
		if (!status) {
			free (cadena);
			return NULL;
		}
	}
	
	int shift = manejador->bitsOcupados;
	for ( int i = 0 ; i < largo ; i++ ) {
		bufferSuperior = ( (manejador->buffer) << shift );
				
		if ( shift == 0 ) {
			bufferInferior = bufferInferior & 0;
		} else {
			status = ManejadorBits_cargar_buffer (manejador);
			if (!status) {
				free (cadena);
				return NULL;
			}
			bufferInferior = ( (manejador->buffer) >> ( 8 - shift ) );
		}
		
		char caracter = ( bufferSuperior | bufferInferior );		
		cadena[i] = caracter;
		
		if ( (shift == 0) && (i < (largo-1)) ) {
			status = ManejadorBits_cargar_buffer (manejador);
			if (!status) {
				free (cadena);
				return NULL;
			}
		}
	}
	manejador->bitsOcupados = shift;
	cadena[largo] = '\0';
	return cadena;
}

/* Destruye el manejador, previo relleno (si se está en el modo escritura
 * y cierre del archivo. */
void ManejadorBits_destruir (ManejadorBits_t* manejador) {
	if (!manejador) return;
	
	if ( (manejador->bitsOcupados > 0) && (manejador->escritura) ) {
		// rellena el archivo con lo que queda en el buffer
		ManejadorBits_bajar_buffer (manejador);
	}
	
	fclose (manejador->archivo);
	free (manejador);
}
